﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using ConfigGen.Core.ConfigurationSettings;
using Moq;
using NUnit.Framework;

namespace ConfigGen.Core.Tests.ConfigurationSettings
{
    [TestFixture]
    public class ConfigurationExpressionEvaluatorTests
    {
        [Test]
        public void Ctor_ArgNullThrowsException()
        {
            Assert.Throws<ArgumentNullException>(() => new ConfigurationExpressionEvaluator(null));
        }

        [Test]
        public void Ctor_CallsToSearchableXmlConverter()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));
    
            new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            mockSearchableXmlConverter.Verify();
        }

        [Test]
        public void PrepareExpression_NullExpresionThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);
            
            string expression = null;
            Assert.Throws<ArgumentNullException> (() => itemUnderTest.PrepareExpression(ref expression));
        }

        [Test]
        public void PrepareExpression_EmptyExpressionThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            string expression = string.Empty;
            Assert.Throws<ArgumentException>(() => itemUnderTest.PrepareExpression(ref expression));
        }

        [Test]
        public void PrepareExpression_ReservedCharactersInExpressionThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            string expression = "$rob=1[]";
            Assert.Throws<ArgumentException>(() => itemUnderTest.PrepareExpression(ref expression));
        }

        [Test]
        public void PrepareExpression_ReturnsLocatedExpressions()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            string expression = "$val1=$val2";
            var locatedTokens = itemUnderTest.PrepareExpression(ref expression);
            Assert.IsNotNull(locatedTokens, "locatedTokens: should not be null");
            Assert.AreEqual(2, locatedTokens.Count(), "locatedTokens: incorrect count");
            Assert.IsNotNull(locatedTokens.FirstOrDefault(t => t == "val1"), "locatedTokens: val1 not found");
            Assert.IsNotNull(locatedTokens.FirstOrDefault(t => t == "val2"), "locatedTokens: val2 not found");
        }

        [Test]
        public void PrepareExpression_RemovesDollarPrefixesFromExpressions()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            string expression = "$val1=$val2";
            var locatedTokens = itemUnderTest.PrepareExpression(ref expression);
            Assert.IsNotNull(expression, "expression: should not be null");
            Assert.AreEqual("val1=val2", expression, "expression: incorrect");
        }

        [Test]
        public void Evaluate_NullMachineNameThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var exception = Assert.Throws<ArgumentNullException>(() => itemUnderTest.Evaluate(null, "$val=1"), "ArgumentNullException expected");
            Assert.AreEqual("machineName", exception.ParamName, "ParamName: incorrect");
        }

        [Test]
        public void Evaluate_EmptyMachineNameThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var exception = Assert.Throws<ArgumentException>(() => itemUnderTest.Evaluate("", "$val=1"), "ArgumentNullException expected");
            Assert.AreEqual("machineName", exception.ParamName, "ParamName: incorrect");
        }

        [Test]
        public void Evaluate_NullExpressionThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var exception = Assert.Throws<ArgumentNullException>(() => itemUnderTest.Evaluate("machineName", null), "ArgumentNullException expected");
            Assert.AreEqual("expression", exception.ParamName, "ParamName: incorrect");
        }

        [Test]
        public void Evaluate_EmptyExpressionThrows()
        {
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteElementString("root", string.Empty));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var exception = Assert.Throws<ArgumentException>(() => itemUnderTest.Evaluate("machineName", ""), "ArgumentNullException expected");
            Assert.AreEqual("expression", exception.ParamName, "ParamName: incorrect");
        }

        [Test]
        public void Evaluate_ExpressionIsTrue()
        {
            const string xml = @"
<MachineConfigurationRoot>
    <Machines>
        <Machine name='machine1'>
            <Values>
                <MyValue>1</MyValue>
            </Values>
        </Machine>
    </Machines>
</MachineConfigurationRoot>";
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteRaw(xml));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var result = itemUnderTest.Evaluate("machine1", "MyValue=1");
            Assert.IsTrue(result);
        }

        [Test]
        public void Evaluate_ExpressionIsFalse()
        {
            const string xml = @"
<MachineConfigurationRoot>
    <Machines>
        <Machine name='machine1'>
            <Values>
                <MyValue>99999</MyValue>
            </Values>
        </Machine>
    </Machines>
</MachineConfigurationRoot>";
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteRaw(xml));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var result = itemUnderTest.Evaluate("machine1", "MyValue=1");
            Assert.IsFalse(result);
        }

        [Test]
        public void Evaluate_MachineDoesNotExist()
        {
            const string xml = @"
<MachineConfigurationRoot>
    <Machines>
        <Machine name='machine99999'>
            <Values>
                <MyValue>1</MyValue>
            </Values>
        </Machine>
    </Machines>
</MachineConfigurationRoot>";
            var machineConfigurationSettings = new List<MachineConfigurationSettings>();
            var machinesConfigurationCollection = new MachinesConfigurationCollection(machineConfigurationSettings);

            var mockSearchableXmlConverter = new Mock<ISearchableXmlConverter>(MockBehavior.Strict);
            mockSearchableXmlConverter
                .Setup(c => c.ToSearchableXml(machinesConfigurationCollection, It.IsAny<XmlWriter>()))
                .Callback((MachinesConfigurationCollection config, XmlWriter writer) => writer.WriteRaw(xml));

            var itemUnderTest = new ConfigurationExpressionEvaluator(machinesConfigurationCollection, mockSearchableXmlConverter.Object);

            var result = itemUnderTest.Evaluate("machine1", "MyValue=1");
            Assert.IsFalse(result);
        }
    }
}
